﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace System
{
    /// <summary>
    /// 表示提供对 <see cref="String"/> 实例的扩展。
    /// </summary>
    public static class StringExtension
    {
        #region IsNullOrEmpty
        /// <summary>
        /// 判断当前的字符串是 null 还是 System.String.Empty 字符串。
        /// <para>示例：<code>if(value.IsNullOrEmpty()){ ... }</code> value 是字符串变量。</para>
        /// </summary>
        /// <param name="value">当前的字符串对象。</param>
        /// <returns>如果 value 参数为 null 或空字符串 ("")，则为 true；否则为 false。</returns>
        public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value);
        #endregion

        #region IsNullOrWhiteSpace
        /// <summary>
        /// 判断当前的字符串是 null, System.String.Empty 字符串还是空白字符。
        /// <para>示例：<code>if(value.IsNullOrWhiteSpace()){ ... }</code> value 是字符串变量。</para>
        /// </summary>
        /// <example>
        /// <code>
        /// var flag="text".IsNullOrWhiteSpace();// return false;
        /// var flag=" ".IsNullOrWhiteSpace();// return true;
        /// </code>
        /// </example>
        /// <param name="value">当前的字符串对象。</param>
        /// <returns>如果 value 参数为 null ，空字符串 ("")或空白字符串，则为 true；否则为 false。</returns>
        public static bool IsNullOrWhiteSpace(this string value) => string.IsNullOrWhiteSpace(value);
        #endregion

        #region IsMatch
        /// <summary>
        /// 判断当前字符串是否与指定模式所匹配。
        /// <para>示例：<code>if(value.IsMatch("正则表达式")){ ... }</code> value 是字符串变量。</para>
        /// </summary>
        /// <param name="value">表示当前需要进行模式匹配的字符串。</param>
        /// <param name="pattern">表示进行模式匹配方式的字符串形式。</param>
        /// <returns>如果匹配，返回 true；否则返回 false。</returns>
        public static bool IsMatch(this string value, string pattern) => Regex.IsMatch(value, pattern);
        #endregion

        #region StringFormat
        /// <summary>
        ///  将当前字符串中的格式项替换为指定数组中相应对象的字符串表示形式。
        /// <para>示例：<code>"{0} + {1} = {2}".StringFormat(1, 2, (1 + 2));</code></para>
        /// </summary>
        /// <param name="format">当前的字符串对象。</param>
        /// <param name="args">字符串format 的一个副本，其中格式项已替换为 args 中相应对象的字符串表示形式。</param>
        /// <example>
        /// <code>
        /// var formatString="这是{0}的示例".StringFormat("自定义数据"); // 输出 这是自定义数据的示例
        /// //同等于
        /// var formatString=string.StringFormat("这是{0}的示例","自定义数据");
        ///
        /// //同样支持多个参数
        /// var formatString="{0} + {1} = {2}".StringFormat(1,2,3); // 输出 1 + 2 = 3
        /// </code>
        /// </example>
        /// <returns>字符串 format 的一个副本，其中格式项已替换为 args 中相应对象的字符串表示形式。</returns>
        public static string StringFormat(this string format, params object[] args) => string.Format(format, args);

        /// <summary>
        /// 将当前字符串中的格式项替换为指定对象的字符串表示形式。
        /// <para>示例：<code>"{0} + {1} = {2}".StringFormat(1, 2, (1 + 2));</code></para>
        /// </summary>
        /// <param name="format">一种带有格式的字符串。</param>
        /// <param name="arg0">符合格式化字符串副本所需替换的对象。</param>
        /// <returns>字符串format 的一个副本，其中格式项已替换为 arg0 中相应对象的字符串表示形式。</returns>
        /// <exception cref="ArgumentNullException">当前字符串对象是 null。</exception>
        /// <exception cref="FormatException">无效的格式化字符串或格式化字符串中的下标不是0.</exception>
        public static string StringFormat(this string format, object arg0) => string.Format(format, arg0);

        /// <summary>
        /// 将当前字符串中的格式项替换为指定对象的字符串表示形式。
        /// <para>示例：<code>"{0} + {1} = {2}".StringFormat(1, 2, (1 + 2));</code></para>
        /// </summary>
        /// <param name="format">一种带有格式的字符串。</param>
        /// <param name="arg0">第一个符合格式化字符串副本所需替换的对象。</param>
        /// <param name="arg1">第二个符合格式化字符串副本所需替换的对象。</param>
        /// <returns>字符串format 的一个副本，其中格式项已替换为 arg0 和 arg1 中相应对象的字符串表示形式。</returns>
        /// <exception cref="ArgumentNullException">当前字符串对象是 null。</exception>
        /// <exception cref="FormatException">无效的格式化字符串或格式化字符串中的下标不是 0 和 1.</exception>
        public static string StringFormat(this string format, object arg0, object arg1) => string.Format(format, arg0, arg1);

        /// <summary>
        /// 将当前字符串中的格式项替换为指定对象的字符串表示形式。
        /// <para>示例：<code>"{0} + {1} = {2}".StringFormat(1, 2, (1 + 2));</code></para>
        /// </summary>
        /// <param name="format">一种带有格式的字符串。</param>
        /// <param name="arg0">第一个符合格式化字符串副本所需替换的对象。</param>
        /// <param name="arg1">第二个符合格式化字符串副本所需替换的对象。</param>
        /// <param name="arg2">第三个符合格式化字符串副本所需替换的对象。</param>
        /// <returns>字符串format 的一个副本，其中格式项已替换为 arg0 、 arg1 和 arg2 中相应对象的字符串表示形式。</returns>
        /// <exception cref="ArgumentNullException">当前字符串对象是 null。</exception>
        /// <exception cref="FormatException">无效的格式化字符串或格式化字符串中的下标不是 0 、1 和 2。</exception>
        public static string StringFormat(this string format, object arg0, object arg1, object arg2) => string.Format(format, arg0, arg1, arg2);
        /// <summary>
        /// 将当前字符串中的格式项替换为指定对象的字符串表示形式。
        /// <para>示例：<code>"{0} + {1} = {2}".StringFormat(1, 2, (1 + 2));</code></para>
        /// </summary>
        /// <param name="format">一种带有格式的字符串。</param>
        /// <param name="provider">一种格式化器。</param>
        /// <param name="args">字符串format 的一个副本，其中格式项已替换为 args 中相应对象的字符串表示形式。</param>
        /// <returns>字符串 format 的一个副本，其中格式项已替换为 args 中相应对象的字符串表示形式。</returns>
        public static string StringFormat(this string format, IFormatProvider provider, params object[] args) => string.Format(provider, format, args);
        #endregion

        #region ToBase64
        /// <summary>
        /// 将当前字符串转换为其用 Base64 数字编码的等效字符串表示形式。
        /// <para>示例：<code>var str = value.ToBase64String();</code> value 是字符串变量。</para>
        /// </summary>
        /// <param name="value">需要转换的字符串。</param>
        /// <param name="encoding">字符编码对象。若为null，则使用 UTF-8 代码页的编码。</param>
        /// <returns>
        /// 字符串 value 的一个副本，将使用指定的字符串格式对其进行 Base64 编码。
        /// </returns>
        /// <exception cref="NotSupportedException">当前指定字符串不支持对 Base64 编码的转换。</exception>
        public static string ToBase64String(this string value, Encoding encoding = default(UTF8Encoding))
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

            try
            {
                byte[] buffer = value.ToBytes(encoding);
                return Convert.ToBase64String(buffer);

            }
            catch (Exception ex)
            {
                throw new NotSupportedException("当前的字符串形式不支持 Base64 格式进行编码，请查看内部异常获取更多信息。", ex);
            }
        }
        #endregion

        #region FromBase64
        /// <summary>
        /// 将当前字符串（它将二进制数据编码为 Base64 数字）转换为等效的字符串。
        /// <para>示例：<code>var str = value.FromBase64String();</code> value 是存储 base64 字符串变量。</para>
        /// </summary>
        /// <param name="value">符合 Base64 编码格式的字符串。</param>
        /// <param name="encoding">字符编码对象。若为 null，则使用 UTF-8 代码页的编码。</param>
        /// <returns>
        /// 符合给定 Base64 编码格式的等效字符串。
        /// </returns>
        /// <exception cref="ArgumentNullException">value</exception>
        /// <exception cref="NotSupportedException">当前指定字符串不支持对 Base64 编码的转换。</exception>
        public static string FromBase64String(this string value, Encoding encoding = default(UTF8Encoding))
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

            try
            {
                byte[] buffer = Convert.FromBase64String(value);
                return buffer.FromBytes(encoding);
            }
            catch (Exception ex)
            {
                throw new NotSupportedException("当前字符串不是有效的 Base64 编码形式，请查看内部异常获取更多信息。", ex);
            }
        }
        #endregion

        #region ToBytes
        /// <summary>
        /// 将当前字符串转换成指定编码格式的字节数组形式。
        /// <para>示例：<code>byte[] buffer = value.ToBytes();</code> value 是一个字符串变量。</para>
        /// </summary>
        /// <param name="value">当前操作的字符串。</param>
        /// <param name="encoding">字符编码对象。若为 null，则使用 UTF-8 编码格式。</param>
        /// <returns>等效于字符串的字节数组。</returns>
        public static byte[] ToBytes(this string value, Encoding encoding = default(UTF8Encoding))
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }

            return encoding.Utf8IfNull().GetBytes(value);
        }
        #endregion

        #region FromBytes
        /// <summary>
        /// 从当前的字节数组中恢复成等效的字符串形式。
        /// <para>示例：<code>string str = buffer.FromBytes();</code> buffer 是一个字符串变量。</para>
        /// </summary>
        /// <param name="bytes">字节数组。</param>
        /// <param name="encoding">字符编码对象。若为 null，则使用 UTF-8 编码格式。</param>
        /// <returns>等效于字节数组的字符串。</returns>
        public static string FromBytes(this byte[] bytes, Encoding encoding = default(UTF8Encoding))
        {
            if (bytes == null)
            {
                throw new ArgumentNullException(nameof(bytes));
            }

            return encoding.Utf8IfNull().GetString(bytes, 0, bytes.Length);
        }
        #endregion

        #region ToMemoryStream
        /// <summary>
        /// 从字符串实例导出成一个 <see cref="MemoryStream"/> 内存流。
        /// <para>
        /// 注意在使用结束后调用 Close() 方法释放流，或使用 using 语句自动释放。
        /// </para>
        /// <para>
        /// 示例：using(var stream = "abcdefg".ToMemoryStream()){ ... }
        /// </para>
        /// </summary>
        /// <param name="value">字符串实例。</param>
        /// <param name="encoding">字符编码对象。若为 null，则使用 UTF-8 编码格式。</param>
        /// <returns>符合当前字符串的内存流。</returns>
        public static MemoryStream ToMemoryStream(this string value, Encoding encoding = default(UTF8Encoding))
            => new MemoryStream(value.ToBytes(encoding));
        #endregion

        #region FromMemoryStream
        /// <summary>
        /// 从 <see cref="MemoryStream" /> 读取并转换成等效的字符串。
        /// <para>
        /// 内部已自动释放该流。
        /// </para><para>
        /// string text = stream.FromMemoryStream(); stream 是一个流对象。可以是 FileStream，MemoryStream 等。
        /// </para>
        /// </summary>
        /// <param name="stream">流的实例。</param>
        /// <param name="encoding">字符编码对象。若为 null，则使用 UTF-8 编码格式。</param>
        /// <returns>
        /// 流实例包含的等效字符串。
        /// </returns>
        /// <exception cref="System.ArgumentNullException">stream 是 null 或已被释放。</exception>
        public static string FromMemoryStream(this MemoryStream stream, Encoding encoding = default(UTF8Encoding))
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            using (stream)
            {
                byte[] buffer = new byte[stream.Length];
                stream.Read(buffer, 0, buffer.Length);
                stream.Close();
                return buffer.FromBytes(encoding);
            }
        }
        #endregion

        #region Utf8IfNull
        /// <summary>
        /// 获取一个编码值，若编码为 null 时，将使用 UTF-8 作为默认编码。
        /// </summary>
        /// <param name="encoding">扩展的 <see cref="Encoding"/> 实例。</param>
        /// <returns>若指定编码是 null，则使用 utf-8 作为编码值，否则为指定的编码值。</returns>
        public static Encoding Utf8IfNull(this Encoding encoding) => encoding ?? Encoding.UTF8;
        #endregion

        #region Substring
        /// <summary>
        /// 从此实例检索子字符串。子字符串从指定的字符位置开始且具有指定的长度，并在末尾追加指定字符串。
        /// 如果子字符串长度小于指定长度，将直接返回该字符串。
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="startIndex">此实例中子字符串的起始字符位置（从零开始）。</param>
        /// <param name="length">子字符串中的字符数。</param>
        /// <param name="append">在末尾追加的字符串。</param>
        /// <returns>与此实例中在 length 处开头、长度为 startIndex 的子字符串等效的一个字符串；如果 System.String.Empty 等于此实例的长度且
        ///     startIndex 为零，则为 length。
        ///     </returns>
        public static string Substring(this string value, int startIndex, int length, string append)
        {
            if (value.Length < length)
            {
                return value;
            }

            return value.Substring(startIndex, length).Append(append);
        }


        /// <summary>
        /// 从此实例检索子字符串。子字符串在指定的字符位置开始并一直到该字符串的末尾，并在末尾追加指定字符串。
        /// 如果子字符串长度小于指定长度，将直接返回该字符串。
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="startIndex">此实例中子字符串的起始字符位置（从零开始）。</param>
        /// <param name="append">在末尾追加的字符串。</param>
        /// <returns>与此实例中在 length 处开头、长度为 startIndex 的子字符串等效的一个字符串；如果 System.String.Empty 等于此实例的长度且
        ///     startIndex 为零，则为 length。
        ///     </returns>
        public static string Substring(this string value, int startIndex, string append)
            => value.Substring(startIndex, value.Length, append);
        #endregion

        #region LastIndexOfRemove
        /// <summary>
        /// 从此实例的最后一个字符开始检索指定的字符，若索引大于0表示成功检索到指定的字符，并移除指定的字符串。
        /// 若检索失败，将直接返回该字符串。
        /// <para>
        /// 应用场景：检测 url 的最后一位是否包含"/"，若包含则需要去掉最后一个的"/"，然后再连接一个新的字符串。
        /// </para>
        /// <para>
        /// 例如配置中 <code>ConfigurationManager.AppSettings["url"].LastIndexOfRemove('/') + "/user/list";</code>，可以确保得到的结果是 "http://www.demo.com/user/list" 而不是 "http://www.demo.com//user/list"
        /// </para>
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="seek">此实例检索的最后一个字符。</param>
        /// <returns>此实例成功检索到最后一个字符并移除相应的字符；若没有检索到指定字符，则返回该字符串。</returns>
        public static string LastIndexOfRemove(this string value, char seek)
        => value.LastIndexOfRemove(seek.ToString());

        /// <summary>
        /// 从此实例的最后一个字符开始检索指定的字符，若索引大于0表示成功检索到指定的字符，并移除指定的字符串。
        /// 若检索失败，将直接返回该字符串。
        /// <para>
        /// 应用场景：检测 url 的最后一位是否包含"/"，若包含则需要去掉最后一个的"/"，然后再连接一个新的字符串。
        /// </para>
        /// <para>
        /// 例如配置中 <code>ConfigurationManager.AppSettings["url"].LastIndexOfRemove("/") + "/user/list";</code>，可以确保得到的结果是 "http://www.demo.com/user/list" 而不是 "http://www.demo.com//user/list"
        /// </para>
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="seek">此实例检索的最后一个字符串。</param>
        /// <returns>此实例成功检索到最后一个字符并移除相应的字符串；若没有检索到指定字符，则返回该字符串。</returns>
        public static string LastIndexOfRemove(this string value, string seek)
        {
            var index = value.LastIndexOf(seek);
            if (index > -1)
            {
                return value.Remove(index, 1);
            }
            return value;
        }
        #endregion

        #region Concat        
        /// <summary>
        /// 从当前字符串追加 <see cref="IEnumerable{T}"/> 的成员并按串联顺序返回字符串。
        /// </summary>
        /// <typeparam name="T">集合的类型。</typeparam>
        /// <param name="value">当前字符串。</param>
        /// <param name="values">一个实现了 <see cref="IEnumerable{T}"/> 的集合成员。</param>
        /// <returns>由当前字符串为第一个并串联集合成员的字符串。</returns>
        public static string Append<T>(this string value, IEnumerable<T> values)
            => "{0}{1}".StringFormat(value, string.Concat(values));

        /// <summary>
        /// 从当前字符串追加指定数量的对象数组，并按给定的顺序返回字符串。
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="args">需要串联的对象数组。</param>
        /// <returns>由当前字符串为第一个并串联给定对象数组的字符串。</returns>
        public static string Append(this string value, params object[] args)
            => "{0}{1}".StringFormat(value, string.Concat(args));

        /// <summary>
        /// 从当前字符串追加指定数量的字符串集合，并按给定的顺序返回字符串。
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="values">需要串联的字符串集合。</param>
        /// <returns>由当前字符串为第一个并串联给定字符串集合的字符串。</returns>
        public static string Append(this string value, IEnumerable<string> values)
            => value.Append(values.ToArray());


        /// <summary>
        /// 从当前字符串追加指定数量的字符串数组，并按给定的顺序返回字符串。
        /// </summary>
        /// <param name="value">当前字符串。</param>
        /// <param name="values">需要串联的字符串数组。</param>
        /// <returns>由当前字符串为第一个并串联给定字符串数组的字符串。</returns>
        public static string Append(this string value, params string[] values)
            => "{0}{1}".StringFormat(value, string.Concat(values));
        #endregion

        #region Join
        /// <summary>
        /// 串联类型为 System.String 的 System.Collections.Generic.IEnumerable`1 构造集合的成员，其中在每个成员之间使用指定的分隔符。
        /// </summary>
        /// <typeparam name="T">指定的数据类型。</typeparam>
        /// <param name="values">一个包含多个连接对象的集合。</param>
        /// <param name="seperator">将用作分隔符的字符串。只有当值具有多个元素时，分隔符才包含在返回的字符串中。</param>
        /// <returns>
        /// 一个由 values 的成员组成的字符串，这些成员以 separator 字符串分隔。
        /// </returns>
        public static string Join<T>(this IEnumerable<T> values, string seperator) => string.Join(seperator, values);

        #endregion
    }
}
